package agents;

import java.io.IOException;
import java.util.HashMap;

import behaviours.SubscribeAndListenBehaviour;

import com.myprofile.profile.MuseumItemDTO;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.domain.FIPANames;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;
import behaviours.Subscriber;

public class CuratorAgent extends Agent implements Subscriber {

	private static final long serialVersionUID = 4249136798635506495L;
	
	private HashMap<String, AID> requests;
	private AID inventory;
	
	@Override
	protected void setup() {
		// Initiate our list of requests
		requests = new HashMap<String, AID>();
		
		// Find our inventory
		findInventory();
		
		// Behaviour for listening for requests from the profiler
		this.addBehaviour(new ListenForItemRequestBehaviour());
		
		// Behaviour for listening for replies from the inventory
		this.addBehaviour(new ListenForRepliesFromInventoryBehaviour());
	}
	
	private class ListenForItemRequestBehaviour extends CyclicBehaviour {

		@Override
		public void action() {

			ACLMessage msg = myAgent.receive(MessageTemplate
					.MatchProtocol(FIPANames.InteractionProtocol.FIPA_REQUEST));

			if (msg != null) {
				
				// Was it a request for an item?
				if(msg.getPerformative() == ACLMessage.REQUEST) {
					System.out.println(myAgent.getName()
							+ ": Item request received.");
					myAgent.addBehaviour(new SendItemRequestBehaviour(msg));
				}
				
				// Was it a request for a curator?
				else if(msg.getPerformative() == ACLMessage.PROPOSE) {
					System.out.println(myAgent.getName()
							+ ": Curator request received.");
					
					// Reply to the request that we are indeed a curator
					ACLMessage reply = msg.createReply();
					reply.setPerformative(ACLMessage.CONFIRM);
					myAgent.send(reply);
				} 
				
				// Was it something else?
				else {
					System.out.println(myAgent.getName()
							+ ": Unknown request received.");
					
					// Reply to the request that we have no clue that they are saying
					ACLMessage reply = msg.createReply();
					reply.setPerformative(ACLMessage.NOT_UNDERSTOOD);
					myAgent.send(reply);
				}
				
				
			} else {
				block();
			}

		}
	}
	
	private class SendItemRequestBehaviour extends OneShotBehaviour {
		
		ACLMessage request;
		
		public SendItemRequestBehaviour(ACLMessage request) {
			this.request = request;
		}

		@Override
		public void action() {
			// What was the id for the item requested
			String requestedItemId = request.getContent();
			
			ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
			msg.setProtocol(FIPANames.InteractionProtocol.FIPA_QUERY);
			msg.setContent(requestedItemId);
			msg.addReceiver(inventory);
			
			System.out.println(myAgent.getName()
					+ ": Passing it on to the inventory agent");
			
			// Add this request to our list of requests
			// When the answer arrives from the inventory, we will look here to see where to send the response
			requests.put(requestedItemId, request.getSender());
			
			myAgent.send(msg);
		}
	}
	
	private class ListenForRepliesFromInventoryBehaviour extends CyclicBehaviour {
		
		@Override
		public void action() {

			ACLMessage msg = myAgent.receive(MessageTemplate
					.MatchProtocol(FIPANames.InteractionProtocol.FIPA_QUERY));

			if (msg != null) {
				// If we got here, the inventory found a matching item for a request
				if(msg.getPerformative() == ACLMessage.INFORM) {
					System.out.println(myAgent.getName()
							+ ": received an INFORM from the inventory. We found a matching item to a request!");

					try {
						// Fetch the item
						MuseumItemDTO item = (MuseumItemDTO) msg.getContentObject();
						
						// Find out who requested the item
						AID requester = requests.remove(item.getId());
						
						// Send a message to him with the item
						myAgent.addBehaviour(new SendItemToRequesterBehaviour(item,requester));
					} 
					// Should not happen
					catch (UnreadableException e) {
						e.printStackTrace();
					}
				}
				// If we got here the inventory couldnt find a matching item for a request
				else if (msg.getPerformative() == ACLMessage.REFUSE) {
					// Find out who requested the item
					// Notify him of the failure
					System.out.println(myAgent.getName()
							+ ": received an REFUSE from the inventory. A requested item could not be found: " + msg.getContent());
					
					// TODO Inform the requester about the failure
				}
			} else {
				block();
			}
		}
	}
	
	private class SendItemToRequesterBehaviour extends OneShotBehaviour {
		
		MuseumItemDTO item;
		AID requester;
		
		public SendItemToRequesterBehaviour(MuseumItemDTO item, AID requester) {
			this.item = item;
			this.requester = requester;
		}

		@Override
		public void action() {
			
			// Make sure we got the proper info
			if(item != null && requester != null) {
				
				// Send a new message to the requester containing the item he wanted
				ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
				msg.setProtocol(FIPANames.InteractionProtocol.FIPA_REQUEST);
				try {
					msg.setContentObject(item);
					msg.addReceiver(requester);
					
					System.out.println(myAgent.getName()
							+ ": sending the requested item to " + requester.getName());
					myAgent.send(msg);
				} // Should not happen
				catch (IOException e) {
					e.printStackTrace();
				}
				
			}
			
		}
	}
	
	private void findInventory() {
		// Subscribe to services to get AID to tourguide
		String[] providers = { "inventory-lookup" };
		
		addBehaviour(new SubscribeAndListenBehaviour(this, providers));
	}
	
	@Override
	public void updateSubscription(String type, AID name) {
		if (type.equals("inventory-lookup")) {
			System.out.println(getName() + ": Found Inventory Agent.");
			inventory = name;
		}
	}
}
